// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-23

package stopper

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/go-libs/console"
	"gitee.com/go-wares/framework-iris/framework/src/commands"
	"os"
	"strconv"
	"strings"
	"syscall"
)

const (
	Description = "Stop iris application service"
	Name        = "stop"
)

type Command struct {
	commands.Common
	name, desc string
	pid        int
	pidFile    string
}

// Before
// check pid.
func (o *Command) Before(_ context.Context, _ *console.Container, _ *console.Command) (err error) {
	var (
		body []byte
		n    int64
		stat os.FileInfo
	)

	o.pidFile = fmt.Sprintf(`%s/%s`, o.PidPath(), o.PidFile())

	// Check pid file.
	if stat, err = os.Stat(o.pidFile); err != nil {
		// Not found.
		if errors.Is(err, os.ErrNotExist) {
			err = fmt.Errorf(`service stopped or not start`)
			return
		}

		// Access denied.
		err = fmt.Errorf(`access denied for check pid file: %s`, o.pidFile)
		return
	}

	// Pid file not a file.
	if stat.IsDir() {
		err = fmt.Errorf(`invalid pid file: %s`, o.pidFile)
		return
	}

	// Pid value.
	if body, err = os.ReadFile(o.pidFile); err != nil {
		err = fmt.Errorf(`can not open pid file: %s`, o.pidFile)
		return
	}

	// Parse pid value.
	if n, err = strconv.ParseInt(strings.TrimSpace(string(body)), 10, 32); err != nil || n <= 0 {
		err = fmt.Errorf(`invalid pid value: %s`, o.pidFile)
		return
	}

	// Pid read succeed.
	o.pid = int(n)
	return
}

// Run
// stopper command.
func (o *Command) Run(_ context.Context, container *console.Container, _ *console.Command) (err error) {
	var p *os.Process

	if p, err = os.FindProcess(o.pid); err != nil {
		return
	}

	// Send TERM signal to the process id.
	if err = p.Signal(syscall.SIGTERM); err != nil {
		if errors.Is(err, os.ErrProcessDone) {
			err = nil
		}
	}

	if err == nil {
		container.GetOutput().Info(`service stopped: pid=%d`, p.Pid)
	}
	return
}

// New
// creates a command instance.
func New() *console.Command {
	var (
		p = &Command{name: Name, desc: Description}
		c = console.NewCommand(p.name).SetDescription(p.desc)
	)

	return c.SetProvider(p)
}
